﻿using LeRenard.Core.Numbers;
using System;
using System.Net.Mail;
using Xunit;

namespace LeRenard.Core.Tests._utils_
{
    internal class SomeClass
    {
        public string ReturnNullString()
        {
            return String.Empty;
        }
    }

    /// <summary>
    /// Tests for <see cref="ThrowIf"/>
    /// </summary>
    public class ThrowIfTests
    {
        [Fact]
        public void Parameter_Selector_Handles_Lambda_Bodies_Correctly()
        {
            // Arrange
            var expected = "String parameter 'smtpClient.Host' in member";
            var actual = String.Empty;

            // Act
            try
            {
                var smtpClient = new SmtpClient("");
                ThrowIf.NullOrEmpty(() => smtpClient.Host);
            }
            catch (Exception ex)
            {
                actual = ex.Message;
                actual = actual.Substring(0, actual.IndexOf("in member") + "in member".Length);
            }

            // Assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public void Parameter_Selector_Handles_Full_Bodies_Correctly()
        {
            // Arrange
            var expected = "String parameter 'someClass.ReturnNullString().Length.ToString().Substring(0, 0)' in member";
            var actual = String.Empty;

            // Act
            try
            {
                var someClass = new SomeClass();
                ThrowIf.NullOrEmpty(() => someClass.ReturnNullString().Length.ToString().Substring(0, 0));
            }
            catch (Exception ex)
            {
                actual = ex.Message;
                actual = actual.Substring(0, actual.IndexOf("in member") + "in member".Length);
            }

            // Assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public void Null_Throws_ArgumentNullException()
        {
            // Arrange
            var expected = typeof(ArgumentNullException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = (object)null;
                ThrowIf.Null(() => member);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void Null_With_TException_Throws_TException()
        {
            // Arrange
            var expected = typeof(MissingMemberException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = (object)null;
                ThrowIf.Null<Object, MissingMemberException>(() => member);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void Null_Returns_Correct_Message()
        {
            // Arrange
            var expected = "Parameter 'member' in member 'Null_Returns_Correct_Message' on line '112' can not be null.";
            var actual = "actual";

            // Act
            try
            {
                var member = (object)null;
                ThrowIf.Null(() => member);
            }
            catch (Exception ex)
            {
                actual = ex.Message;

                // -> removing the "in file 'x:\...\file.cs'" part from the actual string, because this varies from machine to machine.
                var startIndex = actual.IndexOf("in file '");
                var endIndex = actual.IndexOf("on line '");
                var amountOfCharactersToRemove = endIndex - startIndex;
                actual = actual.Remove(startIndex, amountOfCharactersToRemove);

                // -> removing the part after "can not be null.", because some "*Exception" types append their own messages.
                actual = actual.Remove(actual.IndexOf("' can not be null.") + "' can not be null.".Length);
            }

            // Assert
            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void Null_Doesnt_Throw_When_Not_Null()
        {
            // Arrange
            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = new object();
                ThrowIf.Null(() => member);
            };

            // Assert
            Assert.DoesNotThrow(sut);
        }

        [Fact]
        public void NullOrEmpty_Throws_ArgumentNullException_With_Whitespaces()
        {
            // Arrange
            var expected = typeof(ArgumentNullException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = "  ";
                ThrowIf.NullOrEmpty(() => member);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void NullOrEmpty_Throws_ArgumentNullException_With_Null()
        {
            // Arrange
            var expected = typeof(ArgumentNullException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = (string)null;
                ThrowIf.NullOrEmpty(() => member);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void NullOrEmpty_With_TException_Throws_TException()
        {
            // Arrange
            var expected = typeof(MissingMemberException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = "  ";
                ThrowIf.NullOrEmpty<MissingMemberException>(() => member);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void NullOrEmpty_Returns_Correct_Message()
        {
            // Arrange
            var expected = "String parameter 'member' in member 'NullOrEmpty_Returns_Correct_Message' on line '209' can not be null, empty or consist of only whitespace characters.";
            var actual = "actual";

            // Act
            try
            {
                var member = String.Empty;
                ThrowIf.NullOrEmpty(() => member);
            }
            catch (Exception ex)
            {
                actual = ex.Message;

                // -> removing the "in file 'x:\...\file.cs'" part from the actual string, because this varies from machine to machine.
                var startIndex = actual.IndexOf("in file '");
                var endIndex = actual.IndexOf("on line '");
                var amountOfCharactersToRemove = endIndex - startIndex;
                actual = actual.Remove(startIndex, amountOfCharactersToRemove);

                // -> removing the part after "can not be null.", because some "*Exception" types append their own messages.
                actual = actual.Remove(actual.IndexOf("' can not be null, empty or consist of only whitespace characters.") + "' can not be null, empty or consist of only whitespace characters.".Length);
            }

            // Assert
            Assert.Equal<string>(expected, actual);
        }

        [Fact]
        public void NullOrEmpty_Doesnt_Throw_When_Not_Null_Whitespaces_Or_Empty()
        {
            // Arrange
            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = "Not empty.";
                ThrowIf.NullOrEmpty(() => member);
            };

            // Assert
            Assert.DoesNotThrow(sut);
        }

        [Fact]
        public void OutOfRange_Throws_ArgumentOutOfRangeException()
        {
            // Arrange
            var expected = typeof(ArgumentOutOfRangeException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = -1;
                var range = new Int32Range(0);
                ThrowIf.OutOfRange(() => member, range);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void OutOfRange_With_TException_Throws_ArgumentOutOfRangeException()
        {
            // Arrange
            var expected = typeof(DllNotFoundException);

            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = -1;
                var range = new Int32Range(0);
                ThrowIf.OutOfRange<int, DllNotFoundException>(() => member, range);
            };

            // Assert
            Assert.Throws(expected, sut);
        }

        [Fact]
        public void OutOfRange_Doesnt_Throw_When_In_Range()
        {
            // Arrange
            // Act
            Assert.ThrowsDelegate sut = () =>
            {
                var member = 0;
                var range = new Int32Range(0);
                ThrowIf.OutOfRange(() => member, range);
            };

            // Assert
            Assert.DoesNotThrow(sut);
        }
    }
}
